const express = require("express");
const router = express.Router();
const path = require("path");
const utils = require("../utils/index.js");
const moment = require("moment-timezone");
const multer = require("multer");
const AdmZip = require("adm-zip");
const AppInfoParser = require("app-info-parser");
const plist = require("plist");
const fs = require("fs");
const msgUtils = require("../utils/feishu.js");
const crypto = require("crypto");
const CryptoJS = require("crypto-js");
const FormData = require("form-data");
const {
    exec
} = require("child_process");
const config = require("../config");
const axios = require("axios");
const jwt = require("jsonwebtoken");
const pool = require("../poolmuseo.js");


// 配置 multer 的存储设置
const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        // 设置文件上传目录
        cb(null, "/home/wwwroot/default/museoImages");
    },
    filename: function (req, file, cb) {
        // 生成文件名
        cb(null, Date.now() + "-" + file.originalname);
    },
});
//
const upload = multer({ storage: storage });

// 创建文件上传的 POST 接口
router.post("/upload", upload.single("file"), (req, res) => {
    const file = req.file;
    if (!file) {
        return res.status(400).send("请上传一个文件。");
    }
    let fileUrl = ''
    fileUrl = `https://pdf.seamobitech.com/museoImages/${file.filename}`;
    // 返回文件存储地址
    res.send({
        code: 0,
        message: "success",
        fileUrl: fileUrl,
    });
});


router.get('/points/config', async (req, res) => {
    try {
        // 一般只有一条记录，如果将来多条，取最新或者第一条
        const [config] = await mysqlQuery('SELECT * FROM museo_point_config ORDER BY id DESC LIMIT 1');
        res.json({
            code: 0,
            data: config ? config : null
        });
    } catch (error) {
        res.status(500).json({
            success: false,
            error: error.message
        });
    }
});

router.post('/points/product/update', async (req, res) => {
    const { id, product_id, original_price, current_price, points, discount_rate } = req.body;
    if (!product_id || original_price == null || current_price == null || points == null) {
        return res.json({ success: false, msg: '参数错误' });
    }
    try {
        // 判断是新增还是更新
        if (id) {
            // 编辑/更新
            const result = await mysqlQuery(
                'UPDATE museo_points_config SET product_id=?, original_price=?, current_price=?, points=?,discount_rate=?, updated_at=NOW() WHERE id=?',
                [product_id, original_price, current_price, points, discount_rate, id]
            );
            res.json({ success: true, msg: '更新成功', result });
        } else {
            // 新增
            const result = await mysqlQuery(
                'INSERT INTO museo_points_config (product_id, original_price, current_price, points,discount_rate, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW())',
                [product_id, original_price, current_price, points, discount_rate]
            );
            res.json({ success: true, msg: '新增成功', result });
        }
    } catch (err) {
        res.json({ success: false, msg: err.message });
    }
});
router.post('/points/config/update', async (req, res) => {
    const { initial_points, compose_song_cost } = req.body;
    if (initial_points == null || compose_song_cost == null) {
        return res.json({ success: false, msg: '参数缺失' });
    }
    try {
        // 假定ID始终是1，或者用WHERE LIMIT 1都可以
        const result = await mysqlQuery(
            'UPDATE museo_point_config SET initial_points=?, compose_song_cost=?, updated_at=NOW() WHERE id=1',
            [initial_points, compose_song_cost]
        );
        res.json({ success: true, msg: '配置修改成功', result });
    } catch (e) {
        res.json({ success: false, msg: e.message });
    }
});
router.get('/points/products', async (req, res) => {
    try {
        const rows = await mysqlQuery('SELECT * FROM museo_points_config ORDER BY id ASC');
        res.json({
            code: 0,
            list: rows || []
        });
    } catch (error) {
        res.status(500).json({
            success: false,
            error: error.message
        });
    }
});

/**
 * 苹果登录接口 
 * POST /api/login/apple
 * body: { identityToken: string }             
 */
router.post('/login', async (req, res) => {
    try {
        const {
            identifier
        } = req.body;

        if (!identifier) {
            return res.status(400).json({ code: 400, message: "缺少参数" });
        }

        const { user, isNew } = await checkOrCreateUserByType(identifier, 'apple');
        res.send({
            code: 0,
            message: 'success',
            data: {
                user,
                isNew
            }
        });
    } catch (e) {
        console.error(`登录异常:`, e);
        res.status(500).json({ code: 500, message: '服务器异常' });
    }
});
async function checkOrCreateUserByType(identifier, type, gender, interests, country, city) {
    let fieldName = type === 'apple' ? 'apple_identifier' : 'email_identifier';
    let querySql = `SELECT * FROM museo_users WHERE ${fieldName} = ? LIMIT 1`;
    let result = await mysqlQuery(querySql, [identifier]);

    if (result.length > 0) {
        // 老用户，刷新登录时间
        const updateSql = 'UPDATE museo_users SET last_login_time = NOW() WHERE user_id = ?';
        await mysqlQuery(updateSql, [result[0].user_id]);
        let user = result[0];
        [
            'registration_time',
            'last_login_time',
            'created_at',
            'updated_at',
            'subscription_start_date',
            'subscription_expiry_date',
            'invitation_time'
        ].forEach(field => {
            user[field] = user[field]
                ? moment(user[field]).tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm:ss")
                : null;
        });
        return { user, isNew: false };
    } else {
        // 新用户逻辑

        // 1. 查积分配置表拿初始积分
        let [pointConfig] = await mysqlQuery('SELECT initial_points FROM museo_point_config ORDER BY id ASC LIMIT 1');
        let initialPoint = pointConfig ? Number(pointConfig.initial_points) : 0;

        const userId = utils.createId();
        const now = getNowInBeijing();
        const inviteCode = await generateInviteCode();

        let fields = [
            'apple_identifier', 'user_id', 'auth_type',
            'registration_time', 'last_login_time',
            'created_at', 'updated_at', 'invitation_code', 'balance'
        ];
        let values = [
            identifier, userId, type,
            now, now, now, now, inviteCode, initialPoint
        ];
        let insertSql = `INSERT INTO museo_users (${fields.join(',')}) VALUES (${fields.map(() => "?").join(',')})`;
        await mysqlQuery(insertSql, values);

        let rows = await mysqlQuery('SELECT * FROM museo_users WHERE user_id = ?', [userId]);
        let user = rows[0];
        [
            'registration_time',
            'last_login_time',
            'created_at',
            'updated_at',
            'subscription_start_date',
            'subscription_expiry_date',
            'invitation_time'
        ].forEach(field => {
            user[field] = user[field]
                ? moment(user[field]).tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm:ss")
                : null;
        });
        return { user, isNew: true };
    }
}
function getNowInBeijing() {
    return moment().tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm:ss");
}
async function generateInviteCode() {
    // 可以用随机数字字母或第三方生成逻辑
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let code = '';
    for (let i = 0; i < 6; i++) code += chars[Math.floor(Math.random() * chars.length)];
    // 确认不重复
    let retry = 0;
    let exists = true;
    while (exists && retry < 5) {
        const rows = await mysqlQuery('SELECT 1 FROM museo_users WHERE invitation_code = ?', [code]);
        exists = rows.length > 0;
        if (exists) code = chars.split('').sort(() => 0.5 - Math.random()).slice(0, 6).join('');
        retry++;
    }
    return code;
}
//获取用户列表
router.post('/getUserList', async (req, res) => {
    let {
        page,
        size
    } = utils.pageSize(req.body.page, req.body.size);
    let {
        dateRange,
        user_type,
    } = req.body;
    let sqlConditions = [];
    const sqlParams = [];

    if (user_type) {
        sqlConditions.push("user_type = ?");
        sqlParams.push(user_type);
    }
    if (dateRange && dateRange.start && dateRange.end) {
        const startDate = moment(dateRange.start).startOf('day').format('YYYY-MM-DD HH:mm:ss');
        const endDate = moment(dateRange.end).endOf('day').format('YYYY-MM-DD HH:mm:ss');

        sqlConditions.push("created_at BETWEEN ? AND ?");
        sqlParams.push(startDate, endDate);
    }
    let sqlBase = `SELECT * FROM museo_users`;
    let totalBase = "FROM museo_users";

    if (sqlConditions.length > 0) {
        sqlBase += ` WHERE ${sqlConditions.join(" AND ")}`;
        totalBase += ` WHERE ${sqlConditions.join(" AND ")}`;
    }

    let sqlCount = `SELECT COUNT(*) AS total_count ${totalBase}`;

    let {
        total
    } = await utils.getListSumMuseo(sqlCount, sqlParams, res, req);

    sqlBase += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
    sqlParams.push(size, page);

    await pool.query(sqlBase, sqlParams, (err, result) => {
        if (err) return res.send(utils.returnData({
            code: -1,
            err,
            req
        }));
        result.forEach((element) => {
            function fmt(val) {
                return val ? moment(val).tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm:ss") :
                    null;
            }
            element.created_at = fmt(element.created_at);
            element.registration_time = fmt(element.registration_time);
            element.last_login_time = fmt(element.last_login_time);
            element.subscription_start_date = fmt(element.subscription_start_date);
            element.subscription_expiry_date = fmt(element.subscription_expiry_date);
            element.last_active_time = fmt(element.last_active_time);

            function formatDuration(sec) {
                sec = Math.floor(sec);
                if (isNaN(sec) || sec < 0) return '-';
                if (sec < 60) return `${sec}秒`;
                const min = Math.floor(sec / 60);
                const remainSec = sec % 60;
                return remainSec === 0 ?
                    `${min}分钟` :
                    `${min}分${remainSec}秒`;
            }
        });

        res.send(
            utils.returnData({
                data: result,
                total: total,
            })
        );
    });
})
router.post('/getUserInfo', async (req, res) => {
    try {
        const { user_id } = req.body;

        if (!user_id) {
            return res.status(400).json({ code: 400, message: "user_id必填" });
        }

        const rows = await mysqlQuery(`SELECT * FROM museo_users WHERE user_id = ?`, [user_id]);
        if (!rows || rows.length === 0) {
            return res.status(404).json({ code: 404, message: "用户不存在" });
        }
        let user = rows[0];
        // 格式化时间字段
        [
            'registration_time',
            'last_login_time',
            'created_at',
            'updated_at',
            'subscription_start_date',
            'subscription_expiry_date',
            'invitation_time'
        ].forEach(field => {
            user[field] = user[field]
                ? moment(user[field]).tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm:ss")
                : null;
        });

        return res.send({
            code: 0,
            message: 'success',
            data: user
        });
    } catch (e) {
        console.error('获取用户信息异常:', e);
        res.status(500).json({ code: 500, message: "服务器异常" });
    }
});
router.post('/user/account/delete', async (req, res) => {
    const { user_id } = req.body;
    if (!user_id) {
        return res.status(400).json({ code: 400, msg: 'user_id必传' });
    }
    try {
        // 检查用户是否存在
        const rows = await mysqlQuery('SELECT user_id FROM museo_users WHERE user_id = ?', [user_id]);
        if (!rows.length) {
            return res.status(404).json({ code: 404, msg: '账号不存在或已注销' });
        }
        // 注销（物理删除）
        await mysqlQuery('DELETE FROM museo_users WHERE user_id = ?', [user_id]);
        // 你可以按需还加上联删其它附属表内的用户数据（如Recipe_UserRecipe、Recipe_Collect等，需级联delete处理）
        return res.send({ code: 0, msg: 'success' });
    } catch (e) {
        console.error('[user/account/delete] 注销失败:', e);
        res.status(500).json({ code: 500, msg: '服务器异常' });
    }
});
// 用户主动在客户端购买会员时调用（不限首次，也不限上次是否因取消掉过）
router.post('/subscription/activateByClient', async (req, res) => {
    const chinaTime = new Date();
    const { user_id, body } = req.body;
    const moment = require('moment-timezone');

    if (!user_id || !body || typeof body !== 'object') {
        return res.status(400).json({ code: 400, message: '参数错误：user_id 和 body 必须提供' });
    }

    // 1. 检查当前用户是否存在
    const rowsx = await mysqlQuery(`SELECT * FROM museo_users WHERE user_id = ?`, [user_id]);
    if (!rowsx || rowsx.length === 0) {
        return res.status(404).json({ code: 404, message: "用户不存在" });
    }

    const {
        productId,                // 商品ID
        transactionId,            // 当前订单ID
        originalTransactionId,    // 原始订单ID
        purchaseDate,             // 当前订单购买时间（时间戳，毫秒）
        subscriptionExpirationDate, // 订阅过期时间（时间戳，毫秒）
        isTrialPeriod             // 是否试用
    } = body;

    // --- 新增逻辑，开始 ---
    try {
        // a. 检查数据库是否有其他用户占用此 originalTransactionId
        const usersWithThisOTID = await mysqlQuery(
            `SELECT user_id FROM museo_users WHERE original_transaction_id = ? AND user_id != ?`,
            [originalTransactionId, user_id]
        );
        if (usersWithThisOTID && usersWithThisOTID.length > 0) {
            // 有，则批量更新这些账号为普通用户
            await mysqlQuery(
                `UPDATE museo_users SET
                    user_type = 'regular',
                    subscription_start_date = NULL, 
                    subscription_expiry_date = NULL,
                    current_product_id = NULL,
                    transaction_id = NULL,
                    original_transaction_id = NULL
                WHERE original_transaction_id = ? AND user_id != ?`,
                [originalTransactionId, user_id]
            );
        }
        // --- 新增逻辑，结束 ---

        // b. 继续正常会员激活/续费逻辑
        let newUserType;
        let now = Date.now();
        const expires = Number(subscriptionExpirationDate || 0);

        if (isTrialPeriod === true || isTrialPeriod === 'true') {
            newUserType = expires > now ? 'trial_member' : 'regular';
        } else {
            newUserType = expires > now ? 'renewal_member' : 'regular';
        }

        // c. 时间戳转北京时间字符串辅助函数
        function toBeijingStr(ts) {
            if (!ts) return null;
            return moment(Number(ts)).tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm:ss");
        }
        const subscription_start_date = toBeijingStr(purchaseDate);
        const subscription_expiry_date = toBeijingStr(subscriptionExpirationDate);

        // d. 更新目标用户的会员相关信息（会员身份、订单信息、订阅起止时间等）
        const updateSQL = `
            UPDATE museo_users SET
                user_type = ?,
                subscription_start_date = ?,
                subscription_expiry_date = ?,
                current_product_id = ?,
                transaction_id = ?,
                original_transaction_id = ?
            WHERE user_id = ?
        `;
        const updateParams = [
            newUserType,
            subscription_start_date,
            subscription_expiry_date,
            productId || null,
            transactionId || null,
            originalTransactionId || null,
            user_id
        ];
        await mysqlQuery(updateSQL, updateParams);

        // e. 查询并格式化最新用户数据
        const users = await mysqlQuery('SELECT * FROM museo_users WHERE user_id = ?', [user_id]);
        const updatedUser = users[0];
        [
            "subscription_start_date", "subscription_expiry_date",
            "registration_time", "last_login_time", "invitation_time", "created_at", "updated_at"
        ].forEach(field => {
            if (updatedUser[field]) {
                updatedUser[field] = moment(updatedUser[field]).tz("Asia/Shanghai").format("YYYY-MM-DD HH:mm:ss");
            }
        });

        return res.json({ code: 0, msg: '会员激活/购买成功', data: updatedUser });

    } catch (e) {
        console.error('activateByClient error:', e);
        res.status(500).json({ code: 500, message: '服务器错误' });
    }
});

// POST 分页获取用户充值记录
router.post('/recharge/records', async (req, res) => {
    const {
        user_id,
        page = 1,
        pageSize = 10
    } = req.body;

    if (!user_id) {
        return res.status(400).json({
            code: 400,
            msg: '缺少user_id参数'
        });
    }
    const realPage = Math.max(1, Number(page));
    const realPageSize = Math.max(1, Math.min(Number(pageSize), 100));
    const offset = (realPage - 1) * realPageSize;

    try {
        const totalRowsRes = await mysqlQuery(
            'SELECT COUNT(*) AS total FROM museo_points_recharge WHERE user_id = ?',
            [user_id]
        );
        const total = totalRowsRes[0]?.total || 0;

        let rows = await mysqlQuery(
            `SELECT *
             FROM museo_points_recharge
             WHERE user_id = ?
             ORDER BY recharge_time DESC
             LIMIT ? OFFSET ?`, [user_id, realPageSize, offset]
        );

        // 格式化时间和状态
        rows = rows.map(row => ({
            ...row,
            recharge_time: formatDate(row.recharge_time),
            created_at: formatDate(row.created_at),
        }));

        res.send({
            code: 0,
            msg: 'success',
            data: {
                total,
                page: realPage,
                pageSize: realPageSize,
                list: rows
            }
        });
    } catch (e) {
        console.error(e);
        res.status(500).json({
            code: 500,
            msg: '服务器错误'
        });
    }
});
// POST 分页获取用户消费记录
router.post('/consume/records', async (req, res) => {
    const {
        user_id,
        page = 1,
        pageSize = 10
    } = req.body;

    if (!user_id) {
        return res.status(400).json({
            code: 400,
            msg: '缺少user_id参数'
        });
    }
    const realPage = Math.max(1, Number(page));
    const realPageSize = Math.max(1, Math.min(Number(pageSize), 100));
    const offset = (realPage - 1) * realPageSize;

    try {
        const totalRowsRes = await mysqlQuery(
            'SELECT COUNT(*) AS total FROM museo_balance_deduction WHERE user_id = ?',
            [user_id]
        );
        const total = totalRowsRes[0]?.total || 0;

        let rows = await mysqlQuery(
            `SELECT *
             FROM museo_balance_deduction
             WHERE user_id = ?
             ORDER BY created_at DESC
             LIMIT ? OFFSET ?`, [user_id, realPageSize, offset]
        );

        // 格式化各种时间字段
        rows = rows.map(row => ({
            ...row,
            created_at: formatDate(row.created_at),
        }));

        res.send({
            code: 0,
            msg: 'success',
            data: {
                total,
                page: realPage,
                pageSize: realPageSize,
                list: rows
            }
        });
    } catch (e) {
        console.error(e);
        res.status(500).json({
            code: 500,
            msg: '服务器错误'
        });
    }
});
function formatDate(val) {
    if (!val) return '';
    return moment(val).format("YYYY-MM-DD HH:mm:ss");
}

// 增加点击次数，接口 POST /api/template/click
router.post('/template/click', async (req, res) => {
    const { id } = req.body;
    if (!id) {
        return res.json({ success: false, msg: '缺少模板ID' });
    }
    try {
        const result = await mysqlQuery(
            'UPDATE museo_template SET click_count = IFNULL(click_count,0) + 1 WHERE id = ? AND state=0',
            [id]
        );
        if (result.affectedRows > 0) {
            res.json({ code: 0 });
        } else {
            res.json({ code: -1, msg: '未找到该模板或已删除' });
        }
    } catch (e) {
        res.json({ code: -1, msg: e.message });
    }
});

/**
 * 保存用户创作的歌曲（每首歌一条记录）
 * 
 * @param {string} taskId         任务ID（用于关联同批次的歌曲）
 * @param {object[]} clips        歌曲clips数组
 * @param {string} userId         用户ID
 * @param {object} metadata       任务元数据
 */
async function saveClipsToDb(template_id, taskId, clips, userId, metadata = null, mode, extraFields = {}) {
    if (!clips || !clips.length) return;

    const promises = clips.map(clip => {
        const sql = `
    INSERT INTO museo_user_songs
    (id, task_id,template_id, user_id, title, status, audio_url, video_url, image_url, image_large_url,
     is_video_pending, major_model_version, model_name, metadata_json, is_liked,
     display_name, handle, is_handle_updated, avatar_image_url, is_trashed,
     reaction, created_at, play_count, upvote_count, is_public, refund_point,
     mode, genre, mood, recording_type, vocal_gender, is_instrumental, completed_at, updated_at)
    VALUES (?, ?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NOW())
    ON DUPLICATE KEY UPDATE
      title = VALUES(title),
      status = VALUES(status),
      audio_url = VALUES(audio_url),
      video_url = VALUES(video_url),
      image_url = VALUES(image_url),
      image_large_url = VALUES(image_large_url),
      is_video_pending = VALUES(is_video_pending),
      metadata_json = VALUES(metadata_json),
      is_liked = VALUES(is_liked),
      display_name = VALUES(display_name),
      handle = VALUES(handle),
      is_handle_updated = VALUES(is_handle_updated),
      avatar_image_url = VALUES(avatar_image_url),
      is_trashed = VALUES(is_trashed),
      reaction = VALUES(reaction),
      play_count = VALUES(play_count),
      upvote_count = VALUES(upvote_count),
      is_public = VALUES(is_public),
      refund_point = VALUES(refund_point),
      mode = VALUES(mode),
      genre = VALUES(genre),
      mood = VALUES(mood),
      recording_type = VALUES(recording_type),
      vocal_gender = VALUES(vocal_gender),
      is_instrumental = VALUES(is_instrumental),
      completed_at = VALUES(completed_at),
      updated_at = NOW()
        `;

        const params = [
            clip.id,                     // 1 - id
            taskId,                      // 2 - task_id
            template_id,                      // 3 - user_id
            userId,                      // 3 - user_id
            clip.title || '',            // 4 - title
            clip.status || 'submitted',  // 5 - status
            clip.audio_url || '',        // 6 - audio_url
            clip.video_url || '',        // 7 - video_url
            clip.image_url || null,      // 8 - image_url
            clip.image_large_url || null,// 9 - image_large_url
            clip.is_video_pending || false, // 10 - is_video_pending
            clip.major_model_version || '', // 11 - major_model_version
            clip.model_name || '',       // 12 - model_name
            JSON.stringify({             // 13 - metadata_json
                ...clip.metadata,
                task_metadata: metadata
            }),
            clip.is_liked || false,      // 14 - is_liked
            clip.display_name || '',     // 15 - display_name
            clip.handle || '',           // 16 - handle
            clip.is_handle_updated || false, // 17 - is_handle_updated
            clip.avatar_image_url || null,   // 18 - avatar_image_url
            clip.is_trashed || false,    // 19 - is_trashed
            clip.reaction || null,       // 20 - reaction
            // created_at 使用 NOW() 函数，不需要参数
            clip.play_count || 0,        // 21 - play_count
            clip.upvote_count || 0,      // 22 - upvote_count
            clip.is_public || false,     // 23 - is_public
            extraFields.refund_point || 0, // 24 - refund_point
            mode,                        // 25 - mode
            extraFields.genre || null,   // 26 - genre
            extraFields.mood || null,    // 27 - mood
            extraFields.recording_type || null, // 28 - recording_type
            extraFields.vocal_gender || null,   // 29 - vocal_gender
            typeof extraFields.is_instrumental === "number" ? extraFields.is_instrumental : 0 // 30 - is_instrumental
            // updated_at 使用 NOW() 函数，不需要参数
        ];

        return mysqlQuery(sql, params);
    });

    return Promise.all(promises);
}



/**
 * 按clipIds数组，更新歌曲的完整信息
 * @param {string[]} clipIds      歌曲ID数组
 * @param {object[]} latestClips  最新的clips数据
 */
async function updateClipsFullInfo(clipIds, latestClips) {
    if (!latestClips || !latestClips.length) return;

    const promises = latestClips.map(clip => {
        const sql = `
            UPDATE museo_user_songs
            SET 
              title = ?,
              status = ?,
              audio_url = ?,
              video_url = ?,
              image_url = ?,
              image_large_url = ?,
              is_video_pending = ?,
              metadata_json = ?,
              is_liked = ?,
              display_name = ?,
              handle = ?,
              is_handle_updated = ?,
              avatar_image_url = ?,
              is_trashed = ?,
              reaction = ?,
              play_count = ?,
              upvote_count = ?,
              is_public = ?,
              completed_at = NOW(),
              updated_at = NOW()
            WHERE id = ?
        `;

        const params = [
            clip.title || '',
            clip.status || 'submitted',
            clip.audio_url || '',
            clip.video_url || '',
            clip.image_url || null,
            clip.image_large_url || null,
            clip.is_video_pending || false,
            JSON.stringify(clip.metadata || {}),
            clip.is_liked || false,
            clip.display_name || '',
            clip.handle || '',
            clip.is_handle_updated || false,
            clip.avatar_image_url || null,
            clip.is_trashed || false,
            clip.reaction || null,
            clip.play_count || 0,
            clip.upvote_count || 0,
            clip.is_public || false,
            clip.id // WHERE条件
        ];

        return mysqlQuery(sql, params);
    });

    return Promise.all(promises);
}

/**
 * 轮询并更新歌曲信息
 * @param {string} taskId    任务ID
 * @param {string[]} clipIds 歌曲ID数组
 */
async function pollAndUpdateSongs(taskId, clipIds) {
    try {
        // 1. 调用API获取最新状态
        const feedUrl = `https://api.openai-hk.com/sunoapi/feed/${clipIds.join(',')}`;
        const resp = await axios.get(feedUrl, {
            headers: {
                'Authorization': 'Bearer hk-bc8rek1000058745307c55077a4cfe005e5d7aefc4841eba',
                'Content-Type': 'application/json'
            }
        });

        // 2. 更新每首歌的完整信息
        const latestClips = resp.data || [];
        await updateClipsFullInfo(clipIds, latestClips);

        // 3. 检查是否全部完成
        const allComplete = latestClips.length > 0 &&
            latestClips.every(clip => clip.status === 'complete');

        console.log(`任务 ${taskId} 轮询结果: ${latestClips.length} 首歌，全部完成: ${allComplete}`);
        return allComplete;
    } catch (error) {
        console.error(`轮询任务 ${taskId} 失败:`, error.message);

        return false;
    }
}

/**
 * 持续轮询直到完成
 * @param {string} taskId     任务ID
 * @param {string[]} clipIds  歌曲ID数组
 * @param {number} maxAttempts 最大尝试次数
 * @param {number} intervalMs  轮询间隔(毫秒)
 */
async function pollUntilComplete(taskId, clipIds, compose_song_cost, user_id, maxAttempts = 60, intervalMs = 5000) {
    console.log(`开始轮询任务 ${taskId}，歌曲数: ${clipIds.length}`);
    let allComplete = false;
    for (let i = 0; i < maxAttempts; i++) {
        allComplete = await pollAndUpdateSongs(taskId, clipIds);
        if (allComplete) {
            console.log(`任务 ${taskId} 已全部完成！`);
            break;
        }

        if (i < maxAttempts - 1) { // 最后一次不用等待
            console.log(`任务 ${taskId} 第 ${i + 1} 次轮询未完成，${intervalMs / 1000}秒后重试...`);
            await new Promise(resolve => setTimeout(resolve, intervalMs));
        }
    }
    // 轮询完了，判断是否需要返还积分
    if (!allComplete) {
        await refundPointsOnce(taskId, user_id, compose_song_cost, pool);
    }
}
/**
 * 根据任务ID安全返还一次积分，并写入积分返还流水表
 * @param {string} taskId
 * @param {string} user_id
 * @param {number} compose_song_cost
 * @param {object} pool - 数据库连接池（推荐用事务，安全性高）
 */
async function refundPointsOnce(taskId, user_id, compose_song_cost, pool) {
    const conn = await new Promise((resolve, reject) =>
        pool.getConnection((err, connection) => (err ? reject(err) : resolve(connection)))
    );
    try {
        await new Promise((resolve, reject) => conn.beginTransaction(err => err ? reject(err) : resolve()));

        // 查询是否有未返还（加 for update 行锁更安全）
        const rows = await new Promise((resolve, reject) =>
            conn.query(
                "SELECT id FROM museo_user_songs WHERE task_id = ? AND refund_point = 0 FOR UPDATE",
                [taskId],
                (err, rows) => err ? reject(err) : resolve(rows))
        );

        if (rows && rows.length) {
            // 1. 查返还前余额
            const [userInfoRow] = await new Promise((resolve, reject) =>
                conn.query('SELECT balance FROM museo_users WHERE user_id = ? FOR UPDATE', [user_id], (err, rows) => err ? reject(err) : resolve(rows))
            );
            const before_balance = userInfoRow ? Number(userInfoRow.balance) : 0;
            const after_balance = before_balance + compose_song_cost;

            // 2. 更新用户积分
            await new Promise((resolve, reject) =>
                conn.query(
                    "UPDATE museo_users SET balance = balance + ? WHERE user_id = ?",
                    [compose_song_cost, user_id],
                    (err, result) => err ? reject(err) : resolve(result)
                )
            );

            // 3. 标记任务下所有歌曲已返还
            await new Promise((resolve, reject) =>
                conn.query(
                    "UPDATE museo_user_songs SET refund_point = 1 WHERE task_id = ? AND refund_point = 0",
                    [taskId],
                    (err, result) => err ? reject(err) : resolve(result)
                )
            );

            // 4. 写积分返还流水
            await new Promise((resolve, reject) =>
                conn.query(
                    `INSERT INTO museo_balance_refund
                     (user_id, task_id, amount, before_balance, after_balance, remark)
                     VALUES (?, ?, ?, ?, ?, ?)`,
                    [user_id, taskId, compose_song_cost, before_balance, after_balance, 'AI生成失败返还'],
                    (err, result) => err ? reject(err) : resolve(result)
                )
            );

            await new Promise((resolve, reject) => conn.commit(err => err ? reject(err) : resolve()));
            console.log(`[返还积分] 任务${taskId}失败，已返还${compose_song_cost}分给用户${user_id}，流水已记录`);
        } else {
            // 无需返还，直接commit
            await new Promise((resolve, reject) => conn.commit(err => err ? reject(err) : resolve()));
        }
        conn.release();
    } catch (err) {
        await new Promise((resolve, reject) => conn.rollback(_ => resolve()));
        conn.release();
        console.error('返还积分失败', err);
        throw err;
    }
}

/**
 * 获取用户的歌曲列表
 * @param {string} userId 用户ID
 * @param {number} page   页码
 * @param {number} limit  每页数量
 */
async function getUserSongs(userId, page = 1, limit = 20) {
    const offset = (page - 1) * limit;
    const sql = `
        SELECT *
        FROM museo_user_songs
        WHERE user_id = ? AND is_trashed = 0
        ORDER BY created_at DESC
        LIMIT ? OFFSET ?
    `;
    return mysqlQuery(sql, [userId, limit, offset]);
}
async function getUserSongsList(page = 1, limit = 20) {
    const offset = (page - 1) * limit;
    const sql = `
        SELECT *
        FROM museo_user_songs
         
        ORDER BY created_at DESC
        LIMIT ? OFFSET ?
    `;
    return mysqlQuery(sql, [limit, offset]);
}
/**
 * 根据任务ID获取该任务下的所有歌曲
 * @param {string} taskId 任务ID
 */
async function getSongsByTaskId(taskId) {
    const sql = `
        SELECT *
        FROM museo_user_songs
        WHERE task_id = ?
        ORDER BY created_at ASC
    `;
    return mysqlQuery(sql, [taskId]);
}
/**
 * 扣除用户积分（balance），插入扣除记录，事务保护
 * @param {object} pool        mysql连接池
 * @param {string} user_id     用户id
 * @param {number} compose_song_cost 需要扣除的积分
 * @param {string} taskId      对应业务的任务id，方便溯源
 * @returns {object}           {code:0,msg:...} 或 {code:401,msg:积分不足}
 */
async function deductUserBalance(pool, user_id, compose_song_cost) {
    // 1. 获取连接
    const conn = await new Promise((resolve, reject) =>
        pool.getConnection((err, connection) => err ? reject(err) : resolve(connection))
    );
    try {
        // 2. 开启事务
        await new Promise((resolve, reject) => conn.beginTransaction(err => err ? reject(err) : resolve()));

        // 3. 查余额加行锁
        const rows = await new Promise((resolve, reject) =>
            conn.query('SELECT balance FROM museo_users WHERE user_id = ? FOR UPDATE', [user_id], (err, rows) => err ? reject(err) : resolve(rows))
        );
        const balance = rows && rows[0] ? Number(rows[0].balance) : 0;
        if (balance < compose_song_cost) {
            await new Promise((resolve, reject) => conn.rollback(err => err ? reject(err) : resolve()));
            conn.release();
            return { code: 401, msg: "积分不足" };
        }

        // 4. 扣除余额
        await new Promise((resolve, reject) =>
            conn.query('UPDATE museo_users SET balance = balance - ? WHERE user_id = ?', [compose_song_cost, user_id],
                (err, result) => err ? reject(err) : resolve(result))
        );

        // 5. 插入积分扣除流水
        const after_balance = balance - compose_song_cost;
        await new Promise((resolve, reject) =>
            conn.query(
                `INSERT INTO museo_balance_deduction 
                 (user_id, amount, before_balance, after_balance, created_at, remark)
                 VALUES (?,?, ?, ?, NOW(), ?)`,
                [user_id, compose_song_cost, balance, after_balance, "AI歌曲生成扣除"],
                (err, result) => err ? reject(err) : resolve(result)
            )
        );

        // 6. 提交事务
        await new Promise((resolve, reject) => conn.commit(err => err ? reject(err) : resolve()));
        conn.release();
        return { code: 0, msg: "扣除成功" };
    } catch (err) {
        await new Promise((resolve, reject) => conn.rollback(_ => resolve()));
        conn.release();
        throw err;
    }
}


//生成歌曲路由
// router.post('/generate', async (req, res) => {
//     try {
//         const {
//             user_id,
//             mode,
//             prompt,
//             make_instrumental,
//             vocal_gender,
//             mv,
//             title,
//             tags,
//             weirdness_constraint,
//             style_weight,
//             genre,
//             mood,
//             recording_type
//         } = req.body;
//         let [pointConfig] = await mysqlQuery('SELECT compose_song_cost FROM museo_point_config ORDER BY id ASC LIMIT 1');
//         let compose_song_cost = pointConfig ? Number(pointConfig.compose_song_cost) : 0;

//         // 获取当前用户积分
//         let [userInfo] = await mysqlQuery('SELECT balance FROM museo_users WHERE user_id = ?', [user_id]);
//         if (!userInfo || userInfo.balance < compose_song_cost) {
//             return res.json({ code: 401, msg: "积分不足" });
//         }



//         // 构建API请求体 (保持原有逻辑)
//         let apiBody = {};
//         let metadata = {};
//         let control_sliders = {};
//         let can_control_sliders = [];

//         if (mode === 'description') {
//             apiBody.gpt_description_prompt = prompt;
//             apiBody.mv = mv || 'chirp-crow';
//             apiBody.prompt = "";
//             if (typeof title === 'string') apiBody.title = title;
//             if (typeof weirdness_constraint === 'number') {
//                 control_sliders.weirdness_constraint = weirdness_constraint;
//                 can_control_sliders.push('weirdness_constraint');
//             }
//             if (typeof style_weight === 'number') {
//                 control_sliders.style_weight = style_weight;
//                 can_control_sliders.push('style_weight');
//             }
//             if (make_instrumental) {
//                 apiBody.make_instrumental = true;
//             }
//             if (!apiBody.make_instrumental && vocal_gender) {
//                 metadata.vocal_gender = vocal_gender;
//             }
//         } else if (mode === 'custom') {
//             apiBody.prompt = prompt || "";
//             apiBody.mv = mv || "chirp-crow";
//             if (typeof title === 'string') apiBody.title = title;
//             if (typeof tags === 'string') apiBody.tags = tags;
//             if (vocal_gender) metadata.vocal_gender = vocal_gender;
//             if (typeof weirdness_constraint === 'number') {
//                 control_sliders.weirdness_constraint = weirdness_constraint;
//                 can_control_sliders.push('weirdness_constraint');
//             }
//             if (typeof style_weight === 'number') {
//                 control_sliders.style_weight = style_weight;
//                 can_control_sliders.push('style_weight');
//             }
//         }

//         if (Object.keys(metadata).length || can_control_sliders.length) {
//             metadata.create_mode = mode || "custom";
//             if (Object.keys(control_sliders).length) metadata.control_sliders = control_sliders;
//             if (can_control_sliders.length) metadata.can_control_sliders = can_control_sliders;
//             apiBody.metadata = metadata;
//         }
//         let is_instrumental = (make_instrumental === undefined || make_instrumental === null || make_instrumental === false || make_instrumental === "")
//             ? 1 : 0;
//         // 调用API
//         const apiUrl = 'https://api.openai-hk.com/sunoapi/generate';
//         const apiResp = await axios.post(apiUrl, apiBody, {
//             headers: {
//                 'Authorization': 'Bearer hk-bc8rek1000058745307c55077a4cfe005e5d7aefc4841eba',
//                 'Content-Type': 'application/json'
//             }
//         });
//         const deductResult = await deductUserBalance(pool, user_id, compose_song_cost);
//         if (deductResult.code === 401) {
//             return res.json(deductResult);
//         }
//         const clips = apiResp.data.clips || [];
//         const clipIds = clips.map(c => c.id);
//         const taskId = apiResp.data.id;

//         await saveClipsToDb(taskId, clips, user_id, apiResp.data.metadata, mode, {
//             genre,
//             mood,
//             recording_type,
//             vocal_gender,
//             is_instrumental
//         });
//         let savedSongs = await getSongsByTaskId(taskId);

//         setImmediate(() => pollUntilComplete(taskId, clipIds, compose_song_cost, user_id));
//         if (savedSongs.length) {
//             savedSongs.forEach(item => {
//                 if (item.metadata_json) {
//                     item.metadata_json = JSON.parse(item.metadata_json)
//                 }
//             })
//         }

//         res.json({
//             code: 0,
//             clips_count: savedSongs.length,
//             songs: savedSongs,
//             apiBody,
//         });

//     } catch (err) {
//         console.error('生成歌曲失败:', err);
//         res.json({
//             code: -1,
//             error: err.message,
//             detail: err.response?.data
//         });
//     }
// });



router.post('/generate', async (req, res) => {
    let parseData = req.body.query;
    let contentx = JSON.parse(decrypt(parseData, config.cryptoKey, config.cryptoIv));
    const chinaTime = new Date();
    if (contentx.code !== 'OZqcJQLBUyevTh') return res.send({ code: 1, msg: 'code错误' });
    try {
        const {
            user_id,
            task_id,              // 可选：如果提供则为重新生成，不提供则为普通生成
            mode,
            prompt,
            make_instrumental,
            vocal_gender,
            mv,
            title,
            tags,
            weirdness_constraint,
            style_weight,
            genre,
            mood,
            recording_type,
            template_id
        } = contentx;

        // 1. 判断是普通生成还是重新生成
        const isRegenerate = !!task_id;
        console.log(isRegenerate ? `重新生成任务: ${task_id}` : '普通生成任务');

        // 2. 获取积分配置
        let [pointConfig] = await mysqlQuery('SELECT compose_song_cost FROM museo_point_config ORDER BY id ASC LIMIT 1');
        let compose_song_cost = pointConfig ? Number(pointConfig.compose_song_cost) : 0;

        // 3. 检查用户积分是否足够
        let [userInfo] = await mysqlQuery('SELECT balance FROM museo_users WHERE user_id = ?', [user_id]);
        if (!userInfo || userInfo.balance < compose_song_cost) {
            return res.json({ code: 401, msg: "积分不足" });
        }

        // 4. 重新生成模式的额外校验
        let originalSongs = [];
        if (isRegenerate) {
            // 检查原任务是否存在且属于当前用户
            originalSongs = await getSongsByTaskId(task_id);
            if (!originalSongs.length) {
                return res.json({ code: 404, msg: "原任务不存在" });
            }

            // 检查任务所有权
            const firstSong = originalSongs[0];
            if (firstSong.user_id !== user_id) {
                return res.json({ code: 403, msg: "无权限操作此任务" });
            }
        }

        // 5. 扣除用户积分
        const deductResult = await deductUserBalance(pool, user_id, compose_song_cost);
        if (deductResult.code === 401) {
            return res.json(deductResult);
        }

        // 6. 重新生成模式：逻辑删除原任务的所有歌曲
        if (isRegenerate) {
            await logicalDeleteSongsByTaskId(task_id);
            console.log(`已逻辑删除原任务 ${task_id} 的所有歌曲`);
        }
        if (template_id) {
            try {
                // 查询模板是否存在且未被删除
                const rows = await mysqlQuery(
                    'SELECT id FROM museo_template WHERE id = ? AND state=0 LIMIT 1',
                    [template_id]
                );
                if (rows && rows.length > 0) {
                    // 有就usage_count+1
                    await mysqlQuery(
                        'UPDATE museo_template SET usage_count = IFNULL(usage_count,0)+1 WHERE id = ?',
                        [template_id]
                    );
                }
            } catch (e) {
                // 异常吞掉，只记录，不影响下面逻辑
                console.error('模板使用次数更新失败', template_id, e);
            }
        }
        // 7. 构建API请求体（原有逻辑不变）
        let apiBody = {};
        let metadata = {};
        let control_sliders = {};
        let can_control_sliders = [];

        if (mode === 'description') {
            apiBody.gpt_description_prompt = prompt;
            apiBody.mv = mv || 'chirp-crow';
            apiBody.prompt = "";
            if (typeof title === 'string') apiBody.title = title;
            if (typeof weirdness_constraint === 'number') {
                control_sliders.weirdness_constraint = weirdness_constraint;
                can_control_sliders.push('weirdness_constraint');
            }
            if (typeof style_weight === 'number') {
                control_sliders.style_weight = style_weight;
                can_control_sliders.push('style_weight');
            }
            if (make_instrumental) {
                apiBody.make_instrumental = true;
            }
            if (!apiBody.make_instrumental && vocal_gender) {
                metadata.vocal_gender = vocal_gender;
            }
        } else if (mode === 'custom') {
            apiBody.prompt = prompt || "";
            apiBody.mv = mv || "chirp-crow";
            if (typeof title === 'string') apiBody.title = title;
            if (typeof tags === 'string') apiBody.tags = tags;
            if (vocal_gender) metadata.vocal_gender = vocal_gender;
            if (typeof weirdness_constraint === 'number') {
                control_sliders.weirdness_constraint = weirdness_constraint;
                can_control_sliders.push('weirdness_constraint');
            }
            if (typeof style_weight === 'number') {
                control_sliders.style_weight = style_weight;
                can_control_sliders.push('style_weight');
            }
        }

        if (Object.keys(metadata).length || can_control_sliders.length) {
            metadata.create_mode = mode || "custom";
            if (Object.keys(control_sliders).length) metadata.control_sliders = control_sliders;
            if (can_control_sliders.length) metadata.can_control_sliders = can_control_sliders;
            apiBody.metadata = metadata;
        }

        let is_instrumental = (make_instrumental === undefined || make_instrumental === null || make_instrumental === false || make_instrumental === "")
            ? 1 : 0;

        // 8. 调用API生成歌曲
        const apiUrl = 'https://api.openai-hk.com/sunoapi/generate';
        let apiResp;
        try {
            apiResp = await axios.post(apiUrl, apiBody, {
                headers: {
                    'Authorization': 'Bearer hk-bc8rek1000058745307c55077a4cfe005e5d7aefc4841eba',
                    'Content-Type': 'application/json'
                }
            });
        } catch (apiError) {
            console.error('API调用失败:', apiError);

            // 重新生成模式：API调用失败时恢复原数据
            if (isRegenerate) {
                await restoreSongsByTaskId(task_id);
                console.log(`API失败，已恢复原任务 ${task_id} 数据`);
            }

            return res.json({
                code: -1,
                msg: isRegenerate ? "重新生成失败，已恢复原数据" : "生成失败",
                error: apiError.message,
                detail: apiError.response?.data
            });
        }

        // 9. 保存新歌曲数据
        const clips = apiResp.data.clips || [];
        const clipIds = clips.map(c => c.id);
        const newTaskId = apiResp.data.id;
        try {
            await saveClipsToDb(template_id, newTaskId, clips, user_id, apiResp.data.metadata, mode, {
                genre,
                mood,
                recording_type,
                vocal_gender,
                is_instrumental
            });
        } catch (error) {
            console.log(error)
        }


        // 10. 获取保存的歌曲数据
        let savedSongs = await getSongsByTaskId(newTaskId);

        // 11. 启动后台轮询任务
        if (isRegenerate) {
            // 重新生成模式：使用特殊的轮询函数
            setImmediate(() => pollUntilCompleteForRegenerate(newTaskId, clipIds, compose_song_cost, user_id, task_id));
        } else {
            // 普通生成模式：使用原有轮询函数
            setImmediate(() => pollUntilComplete(newTaskId, clipIds, compose_song_cost, user_id));
        }

        // 12. 处理返回数据格式
        if (savedSongs.length) {
            savedSongs.forEach(item => {
                if (item.metadata_json) {
                    item.metadata_json = JSON.parse(item.metadata_json)
                }
            })
        }
        const encrypteds = encrypt(JSON.stringify(savedSongs), config.cryptoKey, config.cryptoIv);
        // 13. 返回结果
        const response = {
            code: 0,
            clips_count: savedSongs.length,
            songs: encrypteds

        };
        res.json(response);

    } catch (err) {
        console.error(isRegenerate ? '重新生成歌曲失败:' : '生成歌曲失败:', err);

        // 重新生成模式：发生异常时尝试恢复原数据
        if (isRegenerate && req.body.task_id) {
            try {
                await restoreSongsByTaskId(req.body.task_id);
                console.log(`异常恢复：已恢复任务 ${req.body.task_id} 的数据`);
            } catch (restoreError) {
                console.error('恢复原数据也失败了:', restoreError);
            }
        }

        res.json({
            code: -1,
            msg: isRegenerate ? "重新生成失败" : "生成失败",
            error: err.message,
            detail: err.response?.data
        });
    }
});

/**
 * 逻辑删除指定任务的所有歌曲
 */
async function logicalDeleteSongsByTaskId(taskId) {
    const sql = `
        UPDATE museo_user_songs 
        SET is_trashed = 1, updated_at = NOW()
        WHERE task_id = ? AND is_trashed = 0
    `;
    return mysqlQuery(sql, [taskId]);
}

/**
 * 恢复指定任务的所有歌曲
 */
async function restoreSongsByTaskId(taskId) {
    const sql = `
        UPDATE museo_user_songs 
        SET is_trashed = 0, updated_at = NOW()
        WHERE task_id = ? AND is_trashed = 1
    `;
    return mysqlQuery(sql, [taskId]);
}

/**
 * 重新生成专用轮询函数
 */
async function pollUntilCompleteForRegenerate(newTaskId, clipIds, compose_song_cost, user_id, originalTaskId, maxAttempts = 60, intervalMs = 5000) {
    console.log(`开始轮询重新生成任务 ${newTaskId}，原任务: ${originalTaskId}`);

    let allComplete = false;

    for (let i = 0; i < maxAttempts; i++) {
        try {
            allComplete = await pollAndUpdateSongs(newTaskId, clipIds);

            if (allComplete) {
                console.log(`重新生成任务 ${newTaskId} 完成！`);
                break;
            }

            if (i < maxAttempts - 1) {
                console.log(`重新生成任务 ${newTaskId} 第 ${i + 1} 次轮询未完成，等待中...`);
                await new Promise(resolve => setTimeout(resolve, intervalMs));
            }
        } catch (pollError) {
            console.error(`轮询出错:`, pollError);
        }
    }

    // 轮询失败的恢复处理
    if (!allComplete) {
        console.log(`重新生成任务 ${newTaskId} 超时，开始恢复处理...`);

        try {
            // 返还积分
            await refundPointsOnce(newTaskId, user_id, compose_song_cost, pool);
            // 恢复原数据
            await restoreSongsByTaskId(originalTaskId);
            // 删除失败的新任务
            await logicalDeleteSongsByTaskId(newTaskId);

            console.log(`恢复完成：原任务 ${originalTaskId} 已恢复，新任务 ${newTaskId} 已删除`);
        } catch (recoverError) {
            console.error(`恢复处理失败:`, recoverError);
        }
    }
}

// 获取用户歌曲列表的路由 (改为POST)
router.post('/user/songs', async (req, res) => {
    try {
        const { user_id, page = 1, pageSize = 20 } = req.body;

        if (!user_id) {
            return res.json({
                success: false,
                error: 'user_id is required'
            });
        }

        let songs = await getUserSongs(user_id, parseInt(page), parseInt(pageSize));
        if (songs.length) {
            songs.forEach(item => {
                if (item.metadata_json) {
                    item.metadata_json = JSON.parse(item.metadata_json)
                }
            })
        }
        res.json({
            code: 0,
            data: songs,
            page: parseInt(page),
            pageSize: parseInt(pageSize),
            total: songs.length
        });
    } catch (err) {
        console.error('获取用户歌曲列表失败:', err);
        res.json({
            success: false,
            error: err.message
        });
    }
});
// 辅助函数：格式化为北京时间
function formatToBeijingTime(timeStr) {
    if (!timeStr) return '';
    const d = new Date(timeStr);
    // 北京时间=UTC+8
    // new Date(d.getTime() + 8 * 60 * 60 * 1000) 这种写法是错的！直接取ISO字符串再+8小时才对
    d.setHours(d.getHours() + 8);
    return d.getFullYear() + '-' +
        String(d.getMonth() + 1).padStart(2, '0') + '-' +
        String(d.getDate()).padStart(2, '0') + ' ' +
        String(d.getHours()).padStart(2, '0') + ':' +
        String(d.getMinutes()).padStart(2, '0') + ':' +
        String(d.getSeconds()).padStart(2, '0');
}

// 辅助函数：返回xx分xx秒
function formatDuration(start, end) {
    if (!start || !end) return '';
    const startTs = new Date(start).getTime();
    const endTs = new Date(end).getTime();
    if (isNaN(startTs) || isNaN(endTs)) return '';

    let diffSec = Math.round((endTs - startTs) / 1000);
    if (diffSec < 0) diffSec = 0;
    const min = Math.floor(diffSec / 60);
    const sec = diffSec % 60;
    if (min > 0) return `${min}分${sec}秒`;
    return `${sec}秒`;
}

router.post('/getSongList', async (req, res) => {
    try {
        let {
            page = 1,
            size = 20,
            user_id,
            status,
            mode,
            vocal_gender,
            is_instrumental,
            dateRange // value1: [start, end]
        } = req.body;

        let where = '1=1';
        const params = [];

        if (user_id) {
            where += ' AND user_id = ?';
            params.push(user_id);
        }
        if (status) {
            where += ' AND status = ?';
            params.push(status);
        }
        if (mode) {
            where += ' AND mode = ?';
            params.push(mode);
        }
        if (vocal_gender) {
            where += ' AND vocal_gender = ?';
            params.push(vocal_gender);
        }
        if (typeof is_instrumental !== 'undefined' && is_instrumental !== '') {
            // 注意typeof和''过滤，纯音乐传0，非纯音乐传1，和前端组合对应
            where += ' AND is_instrumental = ?';
            params.push(parseInt(is_instrumental));
        }
        if (dateRange && Array.isArray(dateRange) && dateRange.length === 2 && dateRange[0] && dateRange[1]) {
            where += ' AND created_at BETWEEN ? AND ?';
            params.push(dateRange[0] + ' 00:00:00', dateRange[1] + ' 23:59:59');
        }

        // 分页
        const offset = (parseInt(page) - 1) * parseInt(size);

        // 查询总数
        const countRows = await mysqlQuery(
            `SELECT COUNT(*) as total FROM museo_user_songs WHERE ${where}`,
            params
        );

        // 查询数据
        const songs = await mysqlQuery(
            `SELECT * FROM museo_user_songs WHERE ${where} ORDER BY created_at DESC LIMIT ?, ?`,
            [...params, offset, parseInt(size)]
        );

        if (songs.length) {
            songs.forEach(item => {
                // 1. json转对象
                if (item.metadata_json) {
                    try {
                        item.metadata_json = JSON.parse(item.metadata_json)
                    } catch (e) { /* 忽略异常 */ }
                }
                // 2. 时间格式化
                item.created_at_fmt = formatToBeijingTime(item.created_at);
                item.updated_at_fmt = formatToBeijingTime(item.updated_at);
                item.completed_at_fmt = formatToBeijingTime(item.completed_at);

                // 3. 增加生成耗时字段 duration_fmt
                item.duration_fmt = (item.created_at && item.completed_at)
                    ? formatDuration(item.created_at, item.completed_at)
                    : '';
            });
        }
        res.json({
            code: 0,
            data: songs,
            page: parseInt(page),
            size: parseInt(size),
            total: countRows[0].total
        });
    } catch (err) {
        console.error('获取用户歌曲列表失败:', err);
        res.json({
            success: false,
            error: err.message
        });
    }
});



// 编辑歌曲
router.post('/songs/edit', async (req, res) => {
    try {
        const { id, user_id, title, image_url } = req.body;
        if (!id || !user_id) {
            return res.status(400).json({ success: false, error: '缺少歌曲主键id或user_id' });
        }
        const updates = {};
        if (typeof title === 'string') updates.title = title;
        // 如果传递了 image_url，两个字段都更新
        if (typeof image_url === 'string') {
            updates.image_url = image_url;
            updates.image_large_url = image_url;
        }
        if (Object.keys(updates).length === 0) {
            return res.status(400).json({ success: false, error: '没有可更新内容' });
        }
        const setStr = Object.keys(updates).map(f => `\`${f}\`=?`).join(', ');
        const sql = `UPDATE museo_user_songs SET ${setStr} WHERE id=? AND user_id=? LIMIT 1`;
        const params = [...Object.values(updates), id, user_id];
        const result = await mysqlQuery(sql, params);
        res.json({ code: 0, msg: 'success' });
    } catch (e) {
        res.status(500).json({ success: false, error: e.message });
    }
});
router.post('/songs/delete', async (req, res) => {
    try {
        const { id, user_id } = req.body;
        if (!id || !user_id) {
            return res.status(400).json({ success: false, error: '缺少歌曲主键id或user_id' });
        }
        const sql = `UPDATE museo_user_songs SET is_trashed=1 WHERE id=? AND user_id=? LIMIT 1`;
        const params = [id, user_id];
        const result = await mysqlQuery(sql, params);
        res.json({ code: 0, msg: 'success' });
    } catch (e) {
        res.status(500).json({ success: false, error: e.message });
    }
});




// 根据歌曲ID列表获取歌曲信息的路由
router.post('/songs/batch', async (req, res) => {
    try {
        const { user_id, song_ids } = req.body;

        if (!user_id) {
            return res.json({
                success: false,
                error: 'user_id is required'
            });
        }

        if (!song_ids) {
            return res.json({
                success: false,
                error: 'song_ids is required'
            });
        }

        // 处理歌曲ID字符串，支持逗号分隔
        let songIdArray = [];
        if (typeof song_ids === 'string') {
            songIdArray = song_ids.split(',').map(id => id.trim()).filter(id => id);
        } else if (Array.isArray(song_ids)) {
            songIdArray = song_ids;
        } else {
            return res.json({
                success: false,
                error: 'song_ids must be a string (comma-separated) or array'
            });
        }

        if (songIdArray.length === 0) {
            return res.json({
                success: true,
                data: [],
                count: 0
            });
        }

        let songs = await getSongsByIds(songIdArray, user_id);
        if (songs.length) {
            songs.forEach(item => {
                if (item.metadata_json) {
                    item.metadata_json = JSON.parse(item.metadata_json)
                }
            })
        }
        res.json({
            code: 0,
            data: songs,
            count: songs.length,
            requested_ids: songIdArray
        });
    } catch (err) {
        console.error('批量获取歌曲失败:', err);
        res.json({
            code: -1,
            error: err.message
        });
    }
});
/**
 * 根据歌曲ID列表获取歌曲信息
 * @param {string[]} songIds 歌曲ID数组
 * @param {string} userId 用户ID (用于权限验证)
 */
async function getSongsByIds(songIds, userId = null) {
    if (!songIds || !songIds.length) return [];

    // 构建IN查询的占位符
    const placeholders = songIds.map(() => '?').join(',');

    let sql = `
        SELECT *
        FROM museo_user_songs
        WHERE id IN (${placeholders}) AND is_trashed = 0
    `;
    let params = [...songIds];

    // 如果提供了用户ID，添加用户权限验证
    if (userId) {
        sql += ' AND user_id = ?';
        params.push(userId);
    }

    sql += ' ORDER BY created_at DESC';

    return mysqlQuery(sql, params);
}

//生成描述
router.post("/ai/song-description", async (req, res) => {
    let parseData = req.body.query;
    let contentx = JSON.parse(decrypt(parseData, config.cryptoKey, config.cryptoIv));
    const chinaTime = new Date();
    if (contentx.code !== 'OZqcJQLBUyevTh') return res.send({ code: 1, msg: 'code错误' });
    const { language } = contentx
    try {

        const response = await axios.post(
            'https://api.openai.com/v1/chat/completions',
            {
                model: 'gpt-4.1',
                messages: [
                    {
                        role: 'user',
                        content: [
                            {
                                type: 'text',
                                text: `
Generate a one-sentence song description in ${language} in the format:
"[Mood/Atmosphere] [Genre/Style] song about [Theme/Subject]"
The description should be vivid, creative, and slightly poetic, like a music magazine headline.
Ensure the mood, genre, and theme are expressed naturally in ${language}.`
                            }
                        ]
                    }
                ],
                response_format: {
                    type: 'text'
                },
            },
            {
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer   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`
                }
            }
        );
        let data = encrypt(
            JSON.stringify(response.data.choices[0].message),
            config.cryptoKey,
            config.cryptoIv
        );
        res.send({
            code: 0,
            msg: 'success',
            data: data
        })
    } catch (error) {
        console.error('Error making API request:', error.response ? error.response.data : error.message);
        res.send({
            code: 1,
            msg: error.response ? error.response.data : error.message,
        })
    }

});
//生成歌词
router.post("/ai/song-lyrics", async (req, res) => {
    let parseData = req.body.query;
    let contentx = JSON.parse(decrypt(parseData, config.cryptoKey, config.cryptoIv));
    const chinaTime = new Date();
    if (contentx.code !== 'OZqcJQLBUyevTh') return res.send({ code: 1, msg: 'code错误' });
    const { language, seed_lyrics } = contentx
    try {

        const response = await axios.post(
            'https://api.openai.com/v1/chat/completions',
            {
                model: 'gpt-4.1',
                messages: [
                    {
                        role: 'user',
                        content: [
                            {
                                type: 'text',
                                text: `Generate song lyrics in ${language} with the following structure:
                                        Verse: 4 lines, set the scene and mood
                                        Pre-Chorus: 2–3 lines, create emotional build-up toward the chorus
                                        Chorus: 4 lines, catchy, anthemic, and repeatable
                                        Verse 2: 4 lines, develop or twist the story/theme
                                        Bridge: 3–4 lines, add a shift in perspective or emotional intensity
                                        Chorus: repeat or slightly vary the first chorus for a strong finish
                                        seed_lyrics :${seed_lyrics}
                                        If the parameter seed_lyrics is provided, use it as the starting point:
                                        Keep any part that fits naturally (e.g., if it contains a verse or chorus, use it as part of the output).
                                        Expand on its themes, imagery, and emotional tone to create the rest of the song in a coherent style.
                                        If seed_lyrics is empty, randomly choose a theme, mood, and genre, and generate lyrics from scratch.
                                        The lyrics should feel like a modern, singable song with strong imagery and rhythm. Use internal rhymes, metaphors, and emotional progression to make it engaging.`
                            }
                        ]
                    }
                ],
                response_format: {
                    type: 'text'
                },
            },
            {
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer   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`
                }
            }
        );
        let data = encrypt(
            JSON.stringify(response.data.choices[0].message),
            config.cryptoKey,
            config.cryptoIv
        );
        res.send({
            code: 0,
            msg: 'success',
            data: data
        })
    } catch (error) {
        console.error('Error making API request:', error.response ? error.response.data : error.message);
        res.send({
            code: 1,
            msg: error.response ? error.response.data : error.message,
        })
    }

});

//充值积分
router.post('/recharge', async (req, res) => {
    const {
        user_id,
        transaction_id,
        pk,
        env
    } = req.body;
    if (!user_id || !transaction_id || !pk || !env) {
        return res.status(400).json({
            code: 400,
            msg: '缺少参数'
        });
    }
    console.log(req.body, '充值积分')
    // 1. 构建 JWT Token（具体 createJWT 见 Apple 官方文档）
    let url = null;
    if (env == 'sanbox') {
        url = 'https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/'
    } else {
        url = 'https://api.storekit.itunes.apple.com/inApps/v1/transactions/'

    }
    const jwtToken = createJWT(pk);
    const consumptionUrl = `${url}${transaction_id}`;
    const configAxios = {
        headers: {
            Authorization: `Bearer ${jwtToken}`,
            'Content-Type': 'application/json',
        },
    };

    let conn = null;
    try {
        // 2. 请求苹果API校验交易
        const response = await axios.get(consumptionUrl, configAxios);
        if (!response || !response.data || !response.data.signedTransactionInfo) {
            console.log('充值积分,苹果接口数据异常')
            return res.status(400).json({
                code: 400,
                msg: '苹果接口数据异常'
            });
        }

        const decoded = jwt.decode(response.data.signedTransactionInfo, {
            complete: true
        });
        const data = decoded && decoded.payload;
        console.log(data)
        if (!data) {
            console.log('充值积分,交易凭据解析失败')
            return res.status(400).json({
                code: 400,
                msg: '交易凭据解析失败'
            });
        }

        // 3. 取商户业务字段
        const product_id = data.productId;
        const price = data.price; // 单位分
        const amount = await mapProductToMomoAmount(product_id, pk);
        const currency = data.currency;
        const quantity = data.quantity || 1;

        // ======= 防重放/幂等保障：transactionId只能充值一次 ======
        const [existOrder] = await mysqlQuery(
            'SELECT id FROM museo_points_recharge WHERE transaction_id = ?', [transaction_id]);
        if (existOrder) {
            return res.json({
                code: 0,
                msg: '订单已处理，无需重复入账'
            });
        }
        // 5. 启动事务
        conn = await new Promise((resolve, reject) => {
            pool.getConnection((err, c) => err ? reject(err) : resolve(c));
        });
        await new Promise((resolve, reject) => conn.beginTransaction(err => err ? reject(err) : resolve()));

        // 6. 写入充值记录，所有关键字段都填（amount、transaction_id、product_id、price、pk等）
        const rechargeTime = toMySQLDatetimeUTC(data.purchaseDate);
        const envValue = (env === 'sanbox') ? 'sanbox' : null;
        await mysqlQueryWithConn(
            `INSERT INTO museo_points_recharge
            (user_id, transaction_id, amount,recharge_time,    product_id, price, pk, type, env,created_at)
            VALUES (?, ?,?, ? , ?, ?, ?, ?, ?, ?)`,
            [
                user_id,
                transaction_id,
                amount,
                rechargeTime, // 用苹果实际 purchaseDate
                product_id,
                price,
                pk,
                1,
                envValue,
                utils.formatDate(new Date())
            ],
            conn
        );

        // 7. 更新用户主表余额
        await mysqlQueryWithConn(
            'UPDATE museo_users SET balance = balance + ? WHERE user_id = ?',
            [amount, user_id], conn
        );

        // 8. 查余额，提交
        const [user] = await mysqlQueryWithConn('SELECT balance FROM museo_users WHERE user_id=?', [
            user_id
        ], conn);
        await new Promise((resolve, reject) => conn.commit(err => err ? reject(err) : resolve()));
        conn.release();

        return res.send({
            code: 0,
            msg: 'success',
            data: {
                balance: user ? user.balance : null,
                amount,
                transaction_id,
                product_id,
                price,
                pk,
                expire_time: expireTimeValue,
                currency,
                quantity,
                apple_payload: data
            }
        });

    } catch (err) {
        console.log(err, '充值积分报错')
        if (conn) {
            try {
                await conn.rollback();
                conn.release();
            } catch (e2) { }
        }
        res.status(500).json({
            code: 500,
            msg: '服务器错误'
        });
    }
});
function createJWT(pk) {
    let privateKey = null
    let keyId = null
    let teamId = null
    if (pk == 'com.zhijian.Museo') {
        privateKey = fs.readFileSync('/home/wwwroot/default/iosMuseo-test.p8');
        keyId = 'HFY4Y3693B';
        teamId = '5d6b7154-dee7-4d4e-8a81-77cbb774b30a';
    }

    if (pk == 'com.seamobitech.Museo') {
        privateKey = fs.readFileSync('/home/wwwroot/default/qr.p8');
        keyId = 'BPK84L5D9P';
        teamId = '2beb4915-8807-44e7-a126-e3939cb6ff63';
    }
    const now = Math.floor(Date.now() / 1000);
    const expiresIn = 60 * 60;

    const payload = {
        iss: teamId,
        iat: now,
        exp: now + expiresIn,
        aud: 'appstoreconnect-v1',
        bid: pk
    };

    const token = jwt.sign(payload, privateKey, {
        algorithm: 'ES256',
        keyid: keyId
    });

    return token;
}
router.post('/recharge-test', async (req, res) => {
    const {
        user_id,
        amount,
        expire_time
    } = req.body;
    if (!user_id || !amount || isNaN(amount) || Number(amount) <= 0) {
        return res.status(400).json({
            code: 400,
            msg: '用户ID和充值金额必须合法'
        });
    }


    let conn = null;
    try {
        // 1. 启动事务
        conn = await new Promise((resolve, reject) => {
            pool.getConnection((err, c) => err ? reject(err) : resolve(c));
        });
        await new Promise((resolve, reject) => conn.beginTransaction(err => err ? reject(err) : resolve()));

        // 2. 写入充值记录
        const insertResult = await mysqlQueryWithConn(
            `INSERT INTO museo_points_recharge
                (user_id, amount,   recharge_time,   type) 
             VALUES (?, ?,   NOW(),    2)`,
            [user_id, amount], conn
        );

        // 3. 余额（momo_balance 增加本次充值额度）
        await mysqlQueryWithConn(
            `UPDATE museo_users SET balance = balance + ? WHERE user_id = ?`,
            [amount, user_id], conn
        );

        // 4. 查询最新用户余额
        const [user] = await mysqlQueryWithConn(
            `SELECT balance FROM museo_users WHERE user_id=?`,
            [user_id], conn
        );

        // 5. 提交事务，返回本次充值单ID和新的余额
        await new Promise((resolve, reject) => conn.commit(err => err ? reject(err) : resolve()));
        conn.release();

        return res.json({
            code: 0,
            msg: 'success',
            data: {
                balance: user ? user.balance : null,
                amount
            }
        });
    } catch (err) {
        if (conn) {
            try {
                await conn.rollback();
                conn.release();
            } catch (e2) { }
        }
        console.error(err);
        res.status(500).json({
            code: 500,
            msg: '服务器错误'
        });
    }
});


//后台管理
// 假设你的 questions_json 前端已经 JSON.stringify 了
router.post('/template/save', async (req, res) => {
    let {
        id,
        title,
        content_title,
        sort = 0,
        cover,
        description,
        language = 'zh-CN',
        questions_json
    } = req.body;
    if (!title || !questions_json) {
        return res.json({ success: false, msg: '请填写标题和至少一个问题' });
    }
    questions_json = JSON.stringify(questions_json)

    try {
        if (id) {
            // 编辑
            const result = await mysqlQuery(
                `UPDATE museo_template SET
          title=?, content_title=?, sort=?, cover=?, description=?, questions_json=?, language=?, updated_at=NOW()
         WHERE id=?`,
                [title, content_title, sort, cover, description, questions_json, language, id]
            );
            res.json({ success: true, msg: '编辑成功', result });
        } else {
            // 新增
            const result = await mysqlQuery(
                `INSERT INTO museo_template
          (title, content_title, sort, cover, description, questions_json, language, created_at, updated_at)
         VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
                [title, content_title, sort, cover, description, questions_json, language]
            );
            res.json({ success: true, msg: '新增成功', result });
        }
    } catch (e) {
        res.json({ success: false, msg: e.message });
    }
});
router.post('/template/list', async (req, res) => {
    const { page = 1, pageSize = 20, language, keyword } = req.body;
    let where = 'state=0';
    const params = [];

    if (language) {
        where += ' AND language = ?';
        params.push(language);
    }
    if (keyword) {
        where += ' AND (title LIKE ? OR content_title LIKE ?)';
        params.push(`%${keyword}%`, `%${keyword}%`);
    }

    const offset = (parseInt(page) - 1) * parseInt(pageSize);

    try {
        let countRows = await mysqlQuery(
            `SELECT COUNT(*) as total FROM museo_template WHERE ${where}`,
            params
        );
        let rows = await mysqlQuery(
            `SELECT * FROM museo_template WHERE ${where} ORDER BY sort ASC, id DESC LIMIT ?, ?`,
            [...params, offset, parseInt(pageSize)]
        );

        // 如果查不到数据且传了 language，再查 language='en'
        if (language && (!rows.length)) {
            // 替换where和params
            let enWhere = 'state=0 AND language = ?';
            let enParams = ['en'];
            if (keyword) {
                enWhere += ' AND (title LIKE ? OR content_title LIKE ?)';
                enParams.push(`%${keyword}%`, `%${keyword}%`);
            }
            let enCountRows = await mysqlQuery(
                `SELECT COUNT(*) as total FROM museo_template WHERE ${enWhere}`,
                enParams
            );
            let enRows = await mysqlQuery(
                `SELECT * FROM museo_template WHERE ${enWhere} ORDER BY sort ASC, id DESC LIMIT ?, ?`,
                [...enParams, offset, parseInt(pageSize)]
            );
            rows = enRows;
            countRows = enCountRows;
        }

        if (rows.length) {
            rows.forEach(element => {
                element.questions_json = JSON.parse(element.questions_json)
            });
        }
        res.json({
            code: 0,
            total: countRows[0]?.total || 0,
            list: rows
        });
    } catch (e) {
        res.json({ code: -1, msg: e.message });
    }
});



router.post('/templates/delete', async (req, res) => {
    const { id } = req.body;
    if (!id) {
        return res.json({ code: 400, message: '缺少ID' });
    }
    try {
        const result = await mysqlQuery(
            'UPDATE museo_template SET state=1, updated_at=NOW() WHERE id=?',
            [id]
        );
        res.json({ code: 200, message: '删除成功', result });
    } catch (e) {
        res.json({ code: 500, message: e.message });
    }
});

/**
 * 根据产品ID和pk获取momo币到账数量
 * @param {string} product_id 
 * @param {string} pk 平台标识
 * @returns {Promise<number>}
 */
async function mapProductToMomoAmount(product_id, pk) {
    if (pk === 'com.seamobitech.Museo') {
        // 数据库查币包规则表，获得total_coins
        const rows = await mysqlQuery(
            'SELECT points FROM museo_points_config WHERE product_id=? LIMIT 1',
            [product_id]
        );
        if (rows && rows.length) {
            return rows[0].points || 0;
        } else {
            return 0; // 未配置的档位
        }
    } else {
        // 其它渠道（如安卓、web），可自定义映射表
        const productMap = {
            'museo_20credits': 20,
            'museo_100credits': 100,
            'museo_500credits': 500,
            'museo_3000credits': 3000
        };
        return productMap[product_id] || 0;
    }
}
function toMySQLDatetimeUTC(ts) {
    // ts可以是毫秒时间戳，也可以是Date对象或ISO字符串
    return moment.utc(ts).format('YYYY-MM-DD HH:mm:ss');
}
async function mysqlQuery(sql, arr) {
    return new Promise((resolve, reject) => {
        pool.query(sql, arr, (error, results, fields) => {
            if (error) {
                return reject(error);
            }
            resolve(results);
        });
    });
}
async function mysqlQueryWithConn(sql, arr, conn) {
    return new Promise((resolve, reject) => {
        conn.query(sql, arr, (err, results, fields) => {
            if (err) reject(err);
            else resolve(results);
        });
    });
}
// 加密函数
function encrypt(message, key, iv) {
    const keyHex = CryptoJS.enc.Utf8.parse(key);
    const ivHex = CryptoJS.enc.Utf8.parse(iv);
    // 加密模式为CBC，填充模式为PKCS7
    const encrypted = CryptoJS.DES.encrypt(message, keyHex, {
        iv: ivHex,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7,
    });
    return encrypted.toString();
}

// 解密函数
function decrypt(ciphertext, key, iv) {
    const keyHex = CryptoJS.enc.Utf8.parse(key);
    const ivHex = CryptoJS.enc.Utf8.parse(iv); // 使用key的前8个字符作为IV
    // 解密模式为CBC，填充模式为PKCS7
    const decrypted = CryptoJS.DES.decrypt(
        {
            ciphertext: CryptoJS.enc.Base64.parse(ciphertext),
        },
        keyHex,
        {
            iv: ivHex,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7,
        }
    );
    return decrypted.toString(CryptoJS.enc.Utf8);
}
module.exports = router;